package il.technion.cs236369.webserver.typeHandlers;

import il.technion.cs236369.webserver.JavaCompile;
import il.technion.cs236369.webserver.Request;
import il.technion.cs236369.webserver.Session;
import il.technion.cs236369.webserver.WebServer;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Properties;
import java.util.Random;
import java.util.UUID;
import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.entity.StringEntity;
import org.apache.http.message.BasicHttpResponse;

/**
 * Handles requests of file type TSP (Technion Server Page)
 * @author Yuval and Eddie
 *
 */
public class TSPEngine implements TypeHandler {
	
	// the name of the method that needs to be invoked in the produced class
	// this name is hard-coded in class TspTemplate (any change over here should be done there as well)
	public final static String dynamicClassMethod = "htmlGenerator"; 
	
	private Properties properties;
	private Session session;		//handles the session (if needed)
	private String className;		// the name of the new TSP class	
	private String classPath;		// the path to the .java file created
	private String requestedFile;	// the TSP file requested
	private PrintStream printStream;	//the stream into which the dynamic method prints
	private ByteArrayOutputStream baos;		//used with printStream
	
	public TSPEngine(Properties properties) {
		this.properties = properties;
		session = null;
		printStream = null;
	}

	@Override
	public void handleRequest(Request request) {
		//create default response
		HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,200,"OK");

		// initialize variables for creating the dynamic class
		requestedFile = request.getAbsolutePath();
		Date d = new Date();
		Random r = new Random();
		className = "TSP_"+d.getTime()+"_"+r.nextInt(10000);
		classPath = null;
		
		//parse TSP file and generate dynamic class
		try {
			parseTSPfile();
		} catch (IOException e) {
			e.printStackTrace();
			request.getWebServer().sendErrorResponse(request.getConnection(), 500,
					"Internal Server Error", "TSP file parser error",true);
		}
		
		//check if we need a new session, and adds a session header if we do
		startSession(request, response);
		
		//compile generated file and run it
		compileAndRun(request, response);
		
		//send response
		StringEntity entity;
		try {
			entity = new StringEntity(baos.toString());
			response.setEntity(entity);
		
			// add headers
			response.addHeader("Date", WebServer.generateServerDate());
			response.addHeader("Content-Length", entity.getContentLength() + "");
			response.addHeader("Connection", "close");
			response.addHeader("Content-Type", "text/html");

			//send response
			request.getConnection().sendResponseHeader(response);
			request.getConnection().sendResponseEntity(response);
			request.getConnection().shutdown();
			
		
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			request.getWebServer().sendErrorResponse(request.getConnection(), 500,
					"Internal Server Error", "Dynamic file has unsupported encoding",true);			
		} catch (HttpException e) {
			e.printStackTrace();
			request.getWebServer().sendErrorResponse(request.getConnection(), 500,
					"Internal Server Error", "Unable to send dynamic response",true);
		} catch (IOException e) {
			e.printStackTrace();
			request.getWebServer().sendErrorResponse(request.getConnection(), 500,
					"Internal Server Error", "Unable to send dynamic response",true);
		} finally {
			// delete dynamic files:
			String fileDir = new File("").getAbsolutePath()+"\\";
			File javaFile = new File(fileDir+className+".java");
			if (javaFile.exists()) {
				javaFile.delete();
			}
			File classFile = new File(fileDir+"bin\\il\\technion\\cs236369\\webserver\\typeHandlers\\"+className+".class");
			if (classFile.exists()) {
				classFile.delete();
			}
		}
	}

	/*
	 * if there is a session cookie- associate current session with the correct session
	 * if there is not- start a new session and add a header to the response
	 */
	private void startSession(Request request,HttpResponse response){
		boolean flagFoundSession = false;
		Header[] requestHeaders = request.getHeaders();
		WebServer webServer = request.getWebServer();
		
		for(Header h: requestHeaders){
			if(h.getName().equalsIgnoreCase("Cookie")) {
				UUID sessionID = UUID.fromString(h.getValue());
				if (webServer.getSessionsHandler().isSessionActive(sessionID)){
					session = webServer.getSessionsHandler().getSession(sessionID);
					flagFoundSession = true;
				}
			}
		}
		if (!flagFoundSession){
			//start new session
			session = webServer.getSessionsHandler().startNewSession();
			// add header to the response to the client
			response.addHeader("Set-Cookie", String.valueOf(session.getSessionID()));
		}
		
	}
	
	/*
	 * Parses the TSP file and creates a java file to be compiled
	 */
	private void parseTSPfile() throws IOException {
		File file = new File(className+".java");
		file.createNewFile();
		classPath = file.getAbsolutePath();
		FileWriter fstream = new FileWriter(file);
		BufferedWriter out = new BufferedWriter(fstream);
		writeClassHeads(out);
		
		FileInputStream fstreamin = new FileInputStream(requestedFile);
		DataInputStream in = new DataInputStream(fstreamin);
		BufferedReader br = new BufferedReader(new InputStreamReader(in));
		String strLine = null;
		//Read File Line By Line
		boolean inJava = false;
		while ((strLine = br.readLine()) != null) {
			if (inJava == false) {
				String[] findsJava = strLine.split("<\\?");
				if (findsJava.length == 0) {
					if (strLine.equals("<?")) {
						inJava = true;
					}
					continue;
				}
				findsJava[0] = findsJava[0].replace("\"", "\\\"");
				out.write("out.print(\""+findsJava[0]+"\");\n");
				for (int i=1; i<findsJava.length; i++) {
					inJava = true;
					String[] endsJava = findsJava[i].split("\\?>");
					out.write(endsJava[0]+"\n");
					if (endsJava.length > 1) { // ==> endsJava.length == 2!!
						inJava = false;
						endsJava[1] = endsJava[1].replace("\"", "\\\"");
						out.write("out.print(\""+endsJava[1]+"\");\n");
					} else if (findsJava[i].contains("?>")) {
						inJava = false;
					}
				}
			} else {
				String[] endsJava = strLine.split("\\?>");
				if (endsJava.length == 0) {
					if (strLine.equals("?>")) {
						inJava = false;
					}
					continue;
				}
				out.write(endsJava[0]+"\n");
				for (int i=1; i<endsJava.length; i++) {
					inJava = false;
					String[] findsJava = endsJava[i].split("<\\?");
					findsJava[0] = findsJava[0].replace("\"", "\\\"");
					out.write("out.print(\""+findsJava[0]+"\");\n");
					if (findsJava.length > 1) { // ==> findsJava.length == 2!!
						inJava = true;
						out.write(findsJava[1]+"\n");
					}
				}
				if (endsJava.length==1 && strLine.contains("?>")) {
					inJava = false;
				}
			}
		}
		writeClassfooter(out);
		out.close();
		fstream.close();
	}
	
	private void writeClassHeads(BufferedWriter out) throws IOException {
		out.write("package il.technion.cs236369.webserver.typeHandlers;\n"); // package
		out.write("class "+className+" extends TspTemplate {\n\n"); // class deceleration
		out.write("public " + className + "() { }\n\n"); // empty constructor
		out.write("@Override\n");
		out.write("public void " + dynamicClassMethod + "() {\n"); // dynamic method
	}
	
	private void writeClassfooter(BufferedWriter out) throws IOException {
		out.write("}\n"); // dynamic end
		out.write("}\n"); // class end
		out.flush();
		out.close();
	}
	

	
	/*
	 * compiles generated class and runs it
	 */
	private void compileAndRun(Request request,HttpResponse response) {
		try {
			String compilerPath = properties.getProperty("jre_path");
			
			JavaCompile jc = new JavaCompile(compilerPath);
	
			Class<?> a;
		
			//prepare the qualified class name string. the qualified name of a class is:
			//the package(on this case: the current package) + . + class name
			
			String qualifiedClassName = this.getClass().getPackage().getName() + "." + className;
			
			//compile and load
			a = jc.compileAndLoad(classPath,qualifiedClassName);
			
			if(null == a) {
				//Compilation failure- send response to client and close connection
				request.getWebServer().sendErrorResponse(request.getConnection(), 500,
						"Internal Server Error", "Unable to compile TSP file",true);
			}
			
			//Instantiate
			TspTemplate o = (TspTemplate)a.newInstance();
			
			//initialize variables for the method
			baos = new ByteArrayOutputStream();
			printStream = new PrintStream(baos);
			o.setParams(printStream, session, request.getParamsMap());
			
			//run
			Method m = a.getDeclaredMethod(dynamicClassMethod, new Class<?>[]{});
			m.invoke(o, new Object[]{});
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
			request.getWebServer().sendErrorResponse(request.getConnection(), 500,
					"Internal Server Error", "Dynamic class not found",true);
		} catch (InstantiationException e) {
			e.printStackTrace();
			request.getWebServer().sendErrorResponse(request.getConnection(), 500,
					"Internal Server Error", "Instantiation error",true);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
			request.getWebServer().sendErrorResponse(request.getConnection(), 500,
					"Internal Server Error", "Illegal access",true);
		} catch (SecurityException e) {
			e.printStackTrace();
			request.getWebServer().sendErrorResponse(request.getConnection(), 500,
					"Internal Server Error", "Security issue getting dynamic method",true);
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
			request.getWebServer().sendErrorResponse(request.getConnection(), 500,
					"Internal Server Error", "Method " + dynamicClassMethod + "not found",true);
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
			request.getWebServer().sendErrorResponse(request.getConnection(), 500,
					"Internal Server Error", "Illegal argument when invoking dynamic method",true);
		} catch (InvocationTargetException e) {
			request.getWebServer().sendErrorResponse(request.getConnection(), 500,
					"Internal Server Error", "Dynamic method run failure",true);
			e.printStackTrace();
		}
	}
}
